Deploying Multipaz Servers to Google Cloud Run
Before You Begin
This tutorial guides you through deploying the Multipaz OpenID4VCI Server , Records Server and Verifier Server to Google Cloud Run. These servers work together to provide a complete credential issuance system where the OpenID4VCI server issues credentials based on identity data stored in the Records server. The deployment methods for the three servers are similar. Here, I’ll use the OpenID4VCI and Records servers as examples. The verifier server setup is similar to the OpenID4VCI flow.
Prerequisites
- A Google Cloud Platform (GCP) account with billing enabled
- Google Cloud SDK (
gcloud) installed and configured - Docker installed (for local testing, though Cloud Build handles this)
- Basic understanding of containerization and cloud deployment
- Familiarity with the Multipaz project structure
- Access to the
multipazrepository - Source code pulled/cloned locally: Before running
buildFatJar, you need to pull the source code from themultipazrepository and have it ready for building on your local machine - Google Cloud project with Cloud Run API enabled
- Terminal/command line access
- Java 17+ (for building the JAR files)
What You'll Learn
- How to configure servers for Cloud Run deployment
- How to build fat JAR files with all dependencies
- How to create Docker images for the servers
- How to deploy servers to Google Cloud Run
- How to connect the OpenID4VCI server to the Records server
Configuration Changes
In this commit from the deploy_cloud branch, several configuration changes were made to prepare the servers for Cloud Run deployment:
Changes to multipaz-openid4vci-server
The following changes were made to multipaz-openid4vci-server/src/main/resources/resources/default_configuration.json:
-
Port Configuration: Changed
server_portfrom8007to8080- Why use 8080? Google Cloud Run defaults to port 8080 for all services. Using 8080 ensures your service works without additional configuration.
- If you need a different port: If your service must use a different port (e.g., 9090), you can specify it when deploying:
Then update your
gcloud run deploy multipaz-openid4vci-server \
--image gcr.io/YOUR-PROJECT-ID/multipaz-openid4vci-server \
--port 9090server_portconfiguration to match (e.g.,9090). - The PORT environment variable can override this if needed
-
Base URL Configuration: Added
base_urlpointing to the Cloud Run deployment URL"base_url": "https://multipaz-openid4vci-server-971523157550.us-central1.run.app"⚠️ ImportantReplace this with your own Cloud Run deployment URL after deploying the server
- This ensures all redirects and generated URLs use the correct public URL
- Without this, the server would generate URLs with
localhost:8080
-
System of Record URL: Added
system_of_record_urlpointing to the Records server"system_of_record_url": "https://multipaz-records-server-971523157550.us-central1.run.app"⚠️ ImportantReplace this with your own Records server Cloud Run URL after deploying it
- This connects the OpenID4VCI server to the Records server
- The OpenID4VCI server uses this to retrieve identity data when issuing credentials
Changes to multipaz-records-server
The following changes were made to multipaz-records-server/src/main/resources/resources/default_configuration.json:
-
Port Configuration: Changed
server_portfrom8004to8080- Why use 8080? Google Cloud Run defaults to port 8080 for all services. Using 8080 ensures your service works without additional configuration.
- If you need a different port: If your service must use a different port (e.g., 9090), you can specify it when deploying:
Then update your
gcloud run deploy multipaz-records-server \
--image gcr.io/YOUR-PROJECT-ID/multipaz-records-server \
--port 9090server_portconfiguration to match (e.g.,9090).
-
Base URL Configuration: Added
base_urlpointing to the Cloud Run deployment URL"base_url": "https://multipaz-records-server-971523157550.us-central1.run.app"⚠️ ImportantReplace this with your own Cloud Run deployment URL after deploying the server
- Ensures proper redirects when accessing the root URL
-
Admin Password: Added fixed
admin_passwordset to"multipaz-records"- Previously, the server generated a random password on each startup
- This makes the password predictable and manageable for deployment
-
Database Configuration: Added
database_connection,database_user, anddatabase_passwordfor Cloud SQL"database_connection": "jdbc:postgresql:///multipaz?cloudSqlInstance=YOUR-PROJECT-ID:us-central1:multipaz-db&socketFactory=com.google.cloud.sql.postgres.SocketFactory",
"database_user": "multipaz-records",
"database_password": "multipaz-records"⚠️ Important: Data Persistence RequiredCloud Run containers are ephemeral - any data stored in local files will be lost when the container restarts. To persist data, you must configure a Cloud SQL database (PostgreSQL or MySQL). Without this configuration, the server will use a file-based HSQLDB database that loses all data on container restart.
- Why Cloud SQL? Cloud SQL provides persistent storage that survives container restarts, deployments, and scaling events
- Connection String Format: The connection string uses Cloud SQL's Unix socket connection via the Cloud SQL Proxy
- Replace
YOUR-PROJECT-ID: Update the connection string with your actual GCP project ID - Database Dependencies: The project includes
com.google.cloud.sql:postgres-socket-factorydependency for Cloud SQL connectivity
Deployment Steps
Deploy the Records server first, then update the system_of_record_url in the OpenID4VCI server configuration before deploying it.
Before running the deployment commands, you should activate Cloud Shell from the Google Cloud Run console. Click the "Activate Cloud Shell" button (or press G then S for keyboard shortcut) to open a terminal in your browser where you can run all the commands.
Step 1: Create Dockerfile
Create a Dockerfile for the Records server in the multipaz-records-server directory:
mkdir -p multipaz-records-server && cat > multipaz-records-server/Dockerfile <<'EOF'
FROM eclipse-temurin:17-jre
WORKDIR /app
COPY app.jar app.jar
# Cloud Run sets PORT environment variable; pass it to the server using -param format
CMD ["sh","-c","java -jar app.jar -param server_port=${PORT:-8080}"]
EOF
Command Explanation:
mkdir -p multipaz-records-server: Creates a directory namedmultipaz-records-server(the-pflag creates parent directories if needed and doesn't error if the directory already exists)cat > multipaz-records-server/Dockerfile <<'EOF': Creates a file calledDockerfileinsidemultipaz-records-serverand writes everything untilEOFinto that file- The
<<'EOF'syntax is a "here document" that allows multi-line input - The single quotes around
EOFprevent variable expansion in the content
- The
Dockerfile Contents Explanation:
FROM eclipse-temurin:17-jre: Uses Java 17 JRE base image (matches the server's Java version requirement)WORKDIR /app: Sets the working directory inside the containerCOPY app.jar app.jar: Copies the fat JAR file into the containerCMD: Runs the JAR with the PORT environment variable passed as a parameter- Note: Uses
-param server_port=format (not--server.port=) because this is a Ktor server ${PORT:-8080}uses Cloud Run's PORT environment variable, defaulting to 8080 if not set
- Note: Uses
Step 2: Set Up Cloud SQL for Data Persistence
This step is required to prevent data loss. Without Cloud SQL, all data stored in the Records server will be lost when the container restarts or scales.
Cloud Run containers are ephemeral - any data stored in local files is lost on restart. To persist data, you need to set up a Cloud SQL database:
2.1: Create Cloud SQL Instance
Create a PostgreSQL instance (this typically takes 2-10 minutes):
gcloud sql instances create multipaz-db \
--database-version=POSTGRES_15 \
--tier=db-f1-micro \
--region=us-central1
Explanation:
--database-version=POSTGRES_15: Uses PostgreSQL 15 (you can also use MySQL withMYSQL_8_0)--tier=db-f1-micro: Smallest instance tier (suitable for development/testing)--region=us-central1: Deploy in the same region as your Cloud Run service
2.2: Create Database
Create a database within the instance:
gcloud sql databases create multipaz --instance=multipaz-db
2.3: Create Database User
Create a user for the application:
gcloud sql users create multipaz-records \
--instance=multipaz-db \
--password=multipaz-records
Use a strong password in production. The example uses multipaz-records for simplicity.
2.4: Get Connection Name
Get the connection name for your Cloud SQL instance:
gcloud sql instances describe multipaz-db --format="value(connectionName)"
Output example: YOUR-PROJECT-ID:us-central1:multipaz-db
Save this connection name - you'll need it in the next step.
2.5: Update Configuration File
Update multipaz-records-server/src/main/resources/resources/default_configuration.json with your Cloud SQL connection details:
{
"server_port": 8080,
"base_url": "https://multipaz-records-server-971523157550.us-central1.run.app",
"admin_password": "multipaz-records",
"database_connection": "jdbc:postgresql:///multipaz?cloudSqlInstance=YOUR-PROJECT-ID:us-central1:multipaz-db&socketFactory=com.google.cloud.sql.postgres.SocketFactory",
"database_user": "multipaz-records",
"database_password": "multipaz-records",
...
}
Replace:
YOUR-PROJECT-IDwith your actual GCP project ID (from Step 2.4)multipaz-recordspassword if you used a different password in Step 2.3
For MySQL instead of PostgreSQL:
"database_connection": "jdbc:mysql:///multipaz?cloudSqlInstance=YOUR-PROJECT-ID:us-central1:multipaz-db&socketFactory=com.google.cloud.sql.mysql.SocketFactory"
Instead of hardcoding in the config file, you can pass database credentials via environment variables in Step 7 using --set-env-vars.
Step 3: Build the Fat JAR
Build the fat JAR file that includes all dependencies:
./gradlew :multipaz-records-server:buildFatJar
What this does:
- Compiles the Kotlin code
- Packages all dependencies into a single JAR file
- Includes the configuration files (including
default_configuration.json) - Outputs the JAR to
multipaz-records-server/build/libs/multipaz-records-server-all.jar
Note: The Ktor Gradle plugin creates a fat JAR named multipaz-records-server-all.jar. We need to rename it to app.jar because the Dockerfile expects app.jar.
Step 4: Prepare for Cloud Build
Upload the JAR file to Cloud Shell:
- In the Cloud Shell terminal, click on the three-dot "More" menu icon (⋮) in the top right
- Select Upload from the menu
- A file selection dialog will open. Navigate to and select your
app.jarfile - The file will be uploaded to your home directory (e.g.,
/home/YOUR-PROJECT-ID/multipaz-records-server)- Note:
YOUR-PROJECT-IDis a placeholder - replace it with your actual project name or username
- Note:
Directory structure should look like:
multipaz-records-server/
├── Dockerfile
└── app.jar
Step 5: Create Environment Variables File
Create an env-vars.yaml file in the multipaz-records-server/ directory with the system_of_record_jwk variable:
cat > multipaz-records-server/env-vars.yaml <<'EOF'
system_of_record_jwk: ''
EOF
Use your own JWK - Replace the empty string '' above with your own JWK (JSON Web Key) used for authenticating with the Records server. For generating and using a custom JWK, see Replacing key.jwk in Multipaz Servers.
Step 5: Build Docker Image
Build the Docker image using Google Cloud Build:
gcloud builds submit multipaz-records-server --tag gcr.io/YOUR-PROJECT-ID/multipaz-records-server
Explanation:
gcloud builds submit: Submits a build to Google Cloud Buildmultipaz-records-server: The directory containing the Dockerfile and JAR file--tag: Tags the resulting image with the specified namegcr.io/YOUR-PROJECT-ID/: Your GCP project's Container Registry path- Replace
YOUR-PROJECT-IDwith your actual GCP project ID
- Replace
What happens:
- Cloud Build reads the Dockerfile
- Builds a Docker image containing the JAR file
- Pushes the image to Google Container Registry
- The image is ready to be deployed to Cloud Run
Step 7: Deploy to Cloud Run
Deploy the containerized application to Cloud Run with Cloud SQL connection:
gcloud run deploy multipaz-records-server \
--image gcr.io/YOUR-PROJECT-ID/multipaz-records-server \
--platform managed \
--region us-central1 \
--add-cloudsql-instances=YOUR-PROJECT-ID:us-central1:multipaz-db \
--env-vars-file multipaz-records-server/env-vars.yaml \
--allow-unauthenticated
Parameters explained:
--image: Specifies the Docker image to deploy (from Step 5)--platform managed: Uses Google's fully managed Cloud Run platform--region us-central1: Deploys to the specified region- Choose a region close to your users for better performance
- Important: Should match the region of your Cloud SQL instance
--add-cloudsql-instances: Required for data persistence - Connects the Cloud Run service to your Cloud SQL instance- Format:
PROJECT-ID:REGION:INSTANCE-NAME - Replace
YOUR-PROJECT-IDwith your actual GCP project ID - This enables the Cloud SQL Proxy for secure database connections
- Without this flag, the database connection will fail
- Format:
--port: (Optional) Specifies the port your service listens on- Defaults to 8080 if not specified (Cloud Run's default)
- Only needed if your service uses a different port (e.g.,
--port 9090) - Must match the
server_portin your configuration file
--env-vars-file: Specifies the environment variables file created in Step 5--allow-unauthenticated: Makes the service publicly accessible- Remove this flag if you want to require authentication
After deployment:
- Cloud Run will provide a URL like:
https://multipaz-records-server-XXXXX.us-central1.run.app - Save this URL - you'll need it to update the
system_of_record_urlin the OpenID4VCI server configuration - Update the
base_urlin your Records server configuration file with this URL if needed - Verify data persistence: Upload some data to the Records server, then restart the service. The data should persist, confirming Cloud SQL is working correctly
If the service fails to start or you see database connection errors:
- Verify Cloud SQL Admin API is enabled:
gcloud services enable sqladmin.googleapis.com - Check that the service account has Cloud SQL Client role on the instance
- Verify the connection name in
--add-cloudsql-instancesmatches your instance - Check Cloud Run logs for detailed error messages
Deploying the OpenID4VCI Server
After deploying the Records server, update the system_of_record_url in the OpenID4VCI server configuration with the Records server URL you obtained above, then follow these steps:
Step 1: Update Configuration
Before building, update multipaz-openid4vci-server/src/main/resources/resources/default_configuration.json:
-
Update
system_of_record_urlwith your Records server URL:"system_of_record_url": "https://YOUR-RECORDS-SERVER-URL"⚠️ ImportantReplace
YOUR-RECORDS-SERVER-URLwith the actual URL from your Records server deployment (from Step 6 above). -
Update
base_url(you'll update this again after deployment):"base_url": "https://multipaz-openid4vci-server-971523157550.us-central1.run.app"⚠️ ImportantReplace this with your own Cloud Run deployment URL after deploying the server
Step 2: Create Dockerfile
If you haven't already, activate Cloud Shell from the Google Cloud Run console by clicking "Activate Cloud Shell" (or press G then S for keyboard shortcut) to run the commands below.
Create a Dockerfile for the OpenID4VCI server in the multipaz-openid4vci-server directory:
mkdir -p multipaz-openid4vci-server && cat > multipaz-openid4vci-server/Dockerfile <<'EOF'
FROM eclipse-temurin:17-jre
WORKDIR /app
COPY app.jar app.jar
# Cloud Run sets PORT environment variable; pass it to the server using -param format
CMD ["sh","-c","java -jar app.jar -param server_port=${PORT:-8080}"]
EOF
Command Explanation:
mkdir -p multipaz-openid4vci-server: Creates a directory namedmultipaz-openid4vci-server(the-pflag creates parent directories if needed and doesn't error if the directory already exists)cat > multipaz-openid4vci-server/Dockerfile <<'EOF': Creates a file calledDockerfileinsidemultipaz-openid4vci-serverand writes everything untilEOFinto that file- The
<<'EOF'syntax is a "here document" that allows multi-line input - The single quotes around
EOFprevent variable expansion in the content
- The
Dockerfile Contents Explanation:
FROM eclipse-temurin:17-jre: Uses Java 17 JRE base image (matches the server's Java version requirement)WORKDIR /app: Sets the working directory inside the containerCOPY app.jar app.jar: Copies the fat JAR file into the containerCMD: Runs the JAR with the PORT environment variable passed as a parameter- Note: Uses
-param server_port=format (not--server.port=) because this is a Ktor server ${PORT:-8080}uses Cloud Run's PORT environment variable, defaulting to 8080 if not set
- Note: Uses
Step 3: Build the Fat JAR
Build the fat JAR file that includes all dependencies:
./gradlew :multipaz-openid4vci-server:buildFatJar
What this does:
- Compiles the Kotlin code
- Packages all dependencies into a single JAR file
- Includes the configuration files (including
default_configuration.jsonwith the updatedsystem_of_record_url) - Outputs the JAR to
multipaz-openid4vci-server/build/libs/multipaz-openid4vci-server-all.jar
Note: The Ktor Gradle plugin creates a fat JAR named multipaz-openid4vci-server-all.jar. We need to rename it to app.jar because the Dockerfile expects app.jar.
Step 4: Prepare for Cloud Build
Upload the JAR file to Cloud Shell:
- In the Cloud Shell terminal, click on the three-dot "More" menu icon (⋮) in the top right
- Select Upload from the menu
- A file selection dialog will open. Navigate to and select your
app.jarfile - The file will be uploaded to your home directory (e.g.,
/home/YOUR-PROJECT-ID/multipaz-openid4vci-server)- Note:
YOUR-PROJECT-IDis a placeholder - replace it with your actual project name or username
- Note:
Directory structure should look like:
multipaz-openid4vci-server/
├── Dockerfile
└── app.jar
Step 5: Build Docker Image
Build the Docker image using Google Cloud Build:
gcloud builds submit multipaz-openid4vci-server --tag gcr.io/YOUR-PROJECT-ID/multipaz-openid4vci-server
Explanation:
gcloud builds submit: Submits a build to Google Cloud Buildmultipaz-openid4vci-server: The directory containing the Dockerfile and JAR file--tag: Tags the resulting image with the specified namegcr.io/YOUR-PROJECT-ID/: Your GCP project's Container Registry path- Replace
YOUR-PROJECT-IDwith your actual GCP project ID
- Replace
What happens:
- Cloud Build reads the Dockerfile
- Builds a Docker image containing the JAR file
- Pushes the image to Google Container Registry
- The image is ready to be deployed to Cloud Run
Step 6: Deploy to Cloud Run
Deploy the containerized application to Cloud Run:
gcloud run deploy multipaz-openid4vci-server \
--image gcr.io/YOUR-PROJECT-ID/multipaz-openid4vci-server \
--platform managed \
--region us-central1 \
--allow-unauthenticated
Parameters explained:
--image: Specifies the Docker image to deploy (from Step 5)--platform managed: Uses Google's fully managed Cloud Run platform--region us-central1: Deploys to the specified region- Choose a region close to your users for better performance
--port: (Optional) Specifies the port your service listens on- Defaults to 8080 if not specified (Cloud Run's default)
- Only needed if your service uses a different port (e.g.,
--port 9090) - Must match the
server_portin your configuration file
--allow-unauthenticated: Makes the service publicly accessible- Remove this flag if you want to require authentication
After deployment:
- Cloud Run will provide a URL like:
https://multipaz-openid4vci-server-XXXXX.us-central1.run.app - Update the
base_urlin your configuration file with this URL - Rebuild and redeploy if you need to update the base URL
Verifying Deployment
After deployment, verify both servers are working:
-
Records Server: Visit
https://YOUR-RECORDS-SERVER-URL- You should see the State Registry interface
- Should redirect to
/index.htmlwithout redirecting to localhost - Login with the admin password:
multipaz-records
-
OpenID4VCI Server: Visit
https://YOUR-OPENID4VCI-SERVER-URL- Should redirect to
/index.htmlwithout redirecting to localhost
- Should redirect to
Troubleshooting
Issue: Server redirects to localhost
Solution: Ensure base_url is set correctly in default_configuration.json and rebuild the JAR.
Issue: Port binding errors
Solution: The Dockerfile correctly passes the PORT environment variable. Ensure Cloud Run is setting the PORT variable (it does by default).
Issue: Cannot connect to Records server
Solution:
- Verify the Records server is deployed and accessible
- Check that
system_of_record_urlin OpenID4VCI server matches the Records server URL - Ensure both servers are in the same region or have proper network access
Issue: Build fails with "app.jar not found"
Solution: Ensure you've copied the JAR file to the deployment directory before running gcloud builds submit.
Issue: Data disappears after container restart
Solution:
- Verify you've completed Step 2 (Cloud SQL setup) and configured
database_connectionindefault_configuration.json - Ensure
--add-cloudsql-instancesflag is included in yourgcloud run deploycommand - Check that the Cloud SQL instance is running and accessible
- Verify the database connection string uses the correct project ID and instance name
- Check Cloud Run logs for database connection errors
Issue: Database connection fails
Solution:
- Verify the Cloud SQL instance exists and is running:
gcloud sql instances list - Check that
--add-cloudsql-instancesuses the correct connection name format:PROJECT-ID:REGION:INSTANCE-NAME - Ensure the database user and password in the config match what you created in Cloud SQL
- Verify the database name exists:
gcloud sql databases list --instance=multipaz-db - Check that the Cloud SQL Admin API is enabled
- Ensure the Cloud Run service account has Cloud SQL Client permissions
Summary
In this tutorial, you learned:
- Configuration changes made in this commit to prepare servers for Cloud Run
- How to build fat JARs that include all dependencies
- How to create Dockerfiles for containerizing the servers
- How to build and deploy to Google Cloud Run
- How to connect the OpenID4VCI server to the Records server
The servers are now deployed and ready to issue verifiable credentials based on identity data stored in the Records server.